/**
 * Canvas 绘制器 - 负责所有绘制逻辑
 * 文件行数: < 500 行
 */

import type { Employee, Shift } from '../../types'
import type { CanvasConfig, ViewPort, ScrollbarState } from './types'

export class CanvasDrawer {
  private canvas: HTMLCanvasElement
  private ctx: CanvasRenderingContext2D
  private config: CanvasConfig
  private employees: Employee[]
  private dates: string[]
  private shifts: Shift[]
  private viewport: ViewPort
  private scrollbar: ScrollbarState

  constructor(
    canvas: HTMLCanvasElement,
    ctx: CanvasRenderingContext2D,
    config: CanvasConfig,
    employees: Employee[],
    dates: string[],
    shifts: Shift[],
    viewport: ViewPort
  ) {
    this.canvas = canvas
    this.ctx = ctx
    this.config = config
    this.employees = employees
    this.dates = dates
    this.shifts = shifts
    this.viewport = viewport
    this.scrollbar = this.initScrollbar()
  }

  private initScrollbar(): ScrollbarState {
    return {
      horizontal: {
        visible: true,
        x: 0,
        y: 0,
        width: 0,
        height: this.config.scrollbarSize,
        thumbX: 0,
        thumbWidth: 0,
        isDragging: false,
      },
      vertical: {
        visible: true,
        x: 0,
        y: 0,
        width: this.config.scrollbarSize,
        height: 0,
        thumbY: 0,
        thumbHeight: 0,
        isDragging: false,
      },
    }
  }

  updateData(employees: Employee[], dates: string[], shifts: Shift[], viewport: ViewPort) {
    this.employees = employees
    this.dates = dates
    this.shifts = shifts
    this.viewport = viewport
  }

  getCanvasWidth(): number {
    return this.canvas.width / (window.devicePixelRatio || 1)
  }

  getCanvasHeight(): number {
    return this.canvas.height / (window.devicePixelRatio || 1)
  }

  getTotalContentWidth(): number {
    return this.dates.length * this.config.cellWidth
  }

  getTotalContentHeight(): number {
    return this.employees.length * this.config.cellHeight
  }

  draw() {
    const width = this.getCanvasWidth()
    const height = this.getCanvasHeight()

    // 清空画布
    this.ctx.clearRect(0, 0, width, height)

    // 计算可见区域
    this.calculateVisibleRange()

    // 绘制主内容区域
    this.drawMainContent()

    // 绘制固定表头
    this.drawHeader()

    // 绘制固定部门列（类似甘特图床型列）
    this.drawDepartmentColumn()

    // 绘制固定员工列
    this.drawEmployeeColumn()

    // 绘制左上角
    this.drawCorner()

    // 绘制滚动条
    this.drawScrollbars()
  }

  private calculateVisibleRange() {
    const width = this.getCanvasWidth()
    const height = this.getCanvasHeight()

    // 固定列总宽度（部门列 + 员工列）
    const fixedColumnsWidth = this.config.departmentColumnWidth + this.config.employeeColumnWidth
    const contentWidth = width - fixedColumnsWidth
    const contentHeight = height - this.config.headerHeight

    // 计算可见列范围
    const startCol = Math.floor(this.viewport.scrollX / this.config.cellWidth)
    const endCol = Math.ceil((this.viewport.scrollX + contentWidth) / this.config.cellWidth)

    this.viewport.visibleStartCol = Math.max(0, startCol - 1)
    this.viewport.visibleEndCol = Math.min(this.dates.length, endCol + 1)

    // 计算可见行范围
    const startRow = Math.floor(this.viewport.scrollY / this.config.cellHeight)
    const endRow = Math.ceil((this.viewport.scrollY + contentHeight) / this.config.cellHeight)

    this.viewport.visibleStartRow = Math.max(0, startRow - 1)
    this.viewport.visibleEndRow = Math.min(this.employees.length, endRow + 1)
  }

  private drawMainContent() {
    const { departmentColumnWidth, employeeColumnWidth, headerHeight, cellWidth, cellHeight } =
      this.config
    const fixedColumnsWidth = departmentColumnWidth + employeeColumnWidth
    const ctx = this.ctx

    ctx.save()
    ctx.rect(fixedColumnsWidth, headerHeight, this.getCanvasWidth(), this.getCanvasHeight())
    ctx.clip()

    const offsetX = fixedColumnsWidth - this.viewport.scrollX
    const offsetY = headerHeight - this.viewport.scrollY

    // 绘制网格背景
    ctx.fillStyle = this.config.backgroundColor
    ctx.fillRect(fixedColumnsWidth, headerHeight, this.getCanvasWidth(), this.getCanvasHeight())

    // 绘制网格线
    ctx.strokeStyle = this.config.gridColor
    ctx.lineWidth = 1

    // 垂直线
    for (let i = this.viewport.visibleStartCol; i <= this.viewport.visibleEndCol; i++) {
      const x = offsetX + i * cellWidth
      ctx.beginPath()
      ctx.moveTo(x, headerHeight)
      ctx.lineTo(x, this.getCanvasHeight())
      ctx.stroke()
    }

    // 水平线
    for (let i = this.viewport.visibleStartRow; i <= this.viewport.visibleEndRow; i++) {
      const y = offsetY + i * cellHeight
      ctx.beginPath()
      ctx.moveTo(fixedColumnsWidth, y)
      ctx.lineTo(this.getCanvasWidth(), y)
      ctx.stroke()
    }

    // 绘制班次
    this.drawShifts(offsetX, offsetY)

    ctx.restore()
  }

  private drawShifts(offsetX: number, offsetY: number) {
    const { cellWidth, cellHeight, shiftColors, fontSize } = this.config
    const ctx = this.ctx

    ctx.font = `${fontSize}px ${this.config.fontFamily}`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    for (const shift of this.shifts) {
      // 使用宽松相等 == 来处理 number/string 类型不匹配
      const empIndex = this.employees.findIndex(e => e.id == shift.employeeId)
      const dateIndex = this.dates.findIndex(d => d === shift.date)

      if (empIndex < 0 || dateIndex < 0) continue
      if (empIndex < this.viewport.visibleStartRow || empIndex >= this.viewport.visibleEndRow)
        continue
      if (dateIndex < this.viewport.visibleStartCol || dateIndex >= this.viewport.visibleEndCol)
        continue

      const x = offsetX + dateIndex * cellWidth
      const y = offsetY + empIndex * cellHeight

      // 绘制班次背景
      const color = shiftColors[shift.type as keyof typeof shiftColors] || shiftColors.custom
      ctx.fillStyle = color
      ctx.fillRect(x + 4, y + 4, cellWidth - 8, cellHeight - 8)

      // 绘制班次文字
      ctx.fillStyle = '#ffffff'
      const label = this.getShiftLabel(shift.type)
      ctx.fillText(label, x + cellWidth / 2, y + cellHeight / 2)
    }
  }

  private getShiftLabel(type: string): string {
    const labels: Record<string, string> = {
      morning: '早班',
      afternoon: '中班',
      night: '夜班',
      custom: '自定义',
    }
    return labels[type] || type
  }

  private drawHeader() {
    const { departmentColumnWidth, employeeColumnWidth, headerHeight, cellWidth } = this.config
    const fixedColumnsWidth = departmentColumnWidth + employeeColumnWidth
    const ctx = this.ctx
    const width = this.getCanvasWidth()

    ctx.save()

    // 背景
    ctx.fillStyle = this.config.headerBackgroundColor
    ctx.fillRect(fixedColumnsWidth, 0, width, headerHeight)

    const offsetX = fixedColumnsWidth - this.viewport.scrollX

    // 设置裁剪区域
    ctx.rect(fixedColumnsWidth, 0, width - fixedColumnsWidth, headerHeight)
    ctx.clip()

    // 绘制日期文字
    ctx.fillStyle = '#ffffff'
    ctx.font = `${this.config.fontSize}px ${this.config.fontFamily}`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    for (let i = this.viewport.visibleStartCol; i < this.viewport.visibleEndCol; i++) {
      if (i >= this.dates.length) break

      const x = offsetX + i * cellWidth
      const date = new Date(this.dates[i])
      const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
      const weekDay = weekDays[date.getDay()]
      const month = date.getMonth() + 1
      const day = date.getDate()

      ctx.fillText(weekDay, x + cellWidth / 2, headerHeight / 2 - 8)
      ctx.fillText(`${month}/${day}`, x + cellWidth / 2, headerHeight / 2 + 8)

      // 分隔线
      ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'
      ctx.beginPath()
      ctx.moveTo(x, 0)
      ctx.lineTo(x, headerHeight)
      ctx.stroke()
    }

    ctx.restore()
  }

  private drawDepartmentColumn() {
    // 甘特图床型列样式：相同部门合并单元格显示
    const { departmentColumnWidth, headerHeight, cellHeight } = this.config
    const ctx = this.ctx
    const height = this.getCanvasHeight()

    ctx.save()

    const offsetY = headerHeight - this.viewport.scrollY

    // 设置裁剪区域
    ctx.rect(0, headerHeight, departmentColumnWidth, height - headerHeight)
    ctx.clip()

    // 计算合并的部门单元格
    const mergedDepts: Array<{ dept: string; color: string; startRow: number; endRow: number }> = []
    let currentDept = ''
    let currentColor = ''
    let startRow = 0

    for (let i = 0; i < this.employees.length; i++) {
      const dept = this.employees[i].department || '未分配'
      const color = this.employees[i].departmentColor || '#f5f5f5'

      if (dept !== currentDept) {
        if (currentDept) {
          mergedDepts.push({ dept: currentDept, color: currentColor, startRow, endRow: i })
        }
        currentDept = dept
        currentColor = color
        startRow = i
      }
    }
    // 添加最后一个部门
    if (currentDept) {
      mergedDepts.push({
        dept: currentDept,
        color: currentColor,
        startRow,
        endRow: this.employees.length,
      })
    }

    // 绘制合并的部门单元格（甘特图样式）
    ctx.font = `bold ${this.config.fontSize}px ${this.config.fontFamily}`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    for (const { dept, color, startRow, endRow } of mergedDepts) {
      const y = offsetY + startRow * cellHeight
      const mergedHeight = (endRow - startRow) * cellHeight

      // 只绘制可见区域内的部门
      if (y + mergedHeight < headerHeight || y > height) continue

      // 填充背景色（甘特图床型列样式）
      ctx.fillStyle = color
      ctx.fillRect(0, y, departmentColumnWidth, mergedHeight)

      // 绘制部门名称（垂直居中）
      ctx.fillStyle = this.config.textColor
      ctx.fillText(dept, departmentColumnWidth / 2, y + mergedHeight / 2)

      // 绘制边框
      ctx.strokeStyle = this.config.gridColor
      ctx.lineWidth = 2
      ctx.strokeRect(0, y, departmentColumnWidth, mergedHeight)
    }

    ctx.restore()
  }

  private drawEmployeeColumn() {
    const { departmentColumnWidth, employeeColumnWidth, headerHeight, cellHeight } = this.config
    const ctx = this.ctx
    const height = this.getCanvasHeight()

    ctx.save()

    // 背景
    ctx.fillStyle = this.config.employeeBackgroundColor
    ctx.fillRect(departmentColumnWidth, headerHeight, employeeColumnWidth, height)

    const offsetY = headerHeight - this.viewport.scrollY

    // 设置裁剪区域
    ctx.rect(departmentColumnWidth, headerHeight, employeeColumnWidth, height - headerHeight)
    ctx.clip()

    // 绘制员工名称
    ctx.fillStyle = this.config.textColor
    ctx.font = `${this.config.fontSize}px ${this.config.fontFamily}`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    for (let i = this.viewport.visibleStartRow; i < this.viewport.visibleEndRow; i++) {
      if (i >= this.employees.length) break

      const y = offsetY + i * cellHeight
      const employee = this.employees[i]

      ctx.fillText(
        employee.name,
        departmentColumnWidth + employeeColumnWidth / 2,
        y + cellHeight / 2
      )

      // 分隔线
      ctx.strokeStyle = this.config.gridColor
      ctx.beginPath()
      ctx.moveTo(departmentColumnWidth, y)
      ctx.lineTo(departmentColumnWidth + employeeColumnWidth, y)
      ctx.stroke()
    }

    ctx.restore()
  }

  private drawCorner() {
    const { departmentColumnWidth, employeeColumnWidth, headerHeight } = this.config
    const fixedColumnsWidth = departmentColumnWidth + employeeColumnWidth
    const ctx = this.ctx

    // 绘制整个固定列表头背景
    ctx.fillStyle = this.config.headerBackgroundColor
    ctx.fillRect(0, 0, fixedColumnsWidth, headerHeight)

    ctx.fillStyle = '#ffffff'
    ctx.font = `bold ${this.config.fontSize}px ${this.config.fontFamily}`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    // 绘制部门表头
    ctx.fillText('部门', departmentColumnWidth / 2, headerHeight / 2)

    // 绘制分隔线
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'
    ctx.lineWidth = 2
    ctx.beginPath()
    ctx.moveTo(departmentColumnWidth, 0)
    ctx.lineTo(departmentColumnWidth, headerHeight)
    ctx.stroke()

    // 绘制员工表头
    ctx.fillText('员工', departmentColumnWidth + employeeColumnWidth / 2, headerHeight / 2)
  }

  private drawScrollbars() {
    this.updateScrollbarDimensions()
    this.drawHorizontalScrollbar()
    this.drawVerticalScrollbar()
  }

  private updateScrollbarDimensions() {
    const width = this.getCanvasWidth()
    const height = this.getCanvasHeight()
    const { departmentColumnWidth, employeeColumnWidth, headerHeight, scrollbarSize } = this.config
    const fixedColumnsWidth = departmentColumnWidth + employeeColumnWidth

    const contentWidth = width - fixedColumnsWidth - scrollbarSize
    const contentHeight = height - headerHeight - scrollbarSize
    const totalWidth = this.getTotalContentWidth()
    const totalHeight = this.getTotalContentHeight()

    // 水平滚动条
    const hbar = this.scrollbar.horizontal
    hbar.visible = totalWidth > contentWidth
    hbar.x = fixedColumnsWidth
    hbar.y = height - scrollbarSize
    hbar.width = contentWidth
    hbar.height = scrollbarSize

    if (hbar.visible) {
      const thumbWidth = Math.max(30, (contentWidth / totalWidth) * contentWidth)
      const maxThumbX = contentWidth - thumbWidth
      hbar.thumbWidth = thumbWidth
      hbar.thumbX = (this.viewport.scrollX / (totalWidth - contentWidth)) * maxThumbX
    }

    // 垂直滚动条
    const vbar = this.scrollbar.vertical
    vbar.visible = totalHeight > contentHeight
    vbar.x = width - scrollbarSize
    vbar.y = headerHeight
    vbar.width = scrollbarSize
    vbar.height = contentHeight

    if (vbar.visible) {
      const thumbHeight = Math.max(30, (contentHeight / totalHeight) * contentHeight)
      const maxThumbY = contentHeight - thumbHeight
      vbar.thumbHeight = thumbHeight
      vbar.thumbY = (this.viewport.scrollY / (totalHeight - contentHeight)) * maxThumbY
    }
  }

  private drawHorizontalScrollbar() {
    const { horizontal } = this.scrollbar
    if (!horizontal.visible) return

    const ctx = this.ctx

    // 背景
    ctx.fillStyle = '#f5f5f5'
    ctx.fillRect(horizontal.x, horizontal.y, horizontal.width, horizontal.height)

    // 滑块
    ctx.fillStyle = horizontal.isDragging
      ? this.config.scrollbarHoverColor
      : this.config.scrollbarColor
    ctx.fillRect(
      horizontal.x + horizontal.thumbX,
      horizontal.y + 2,
      horizontal.thumbWidth,
      horizontal.height - 4
    )
  }

  private drawVerticalScrollbar() {
    const { vertical } = this.scrollbar
    if (!vertical.visible) return

    const ctx = this.ctx

    // 背景
    ctx.fillStyle = '#f5f5f5'
    ctx.fillRect(vertical.x, vertical.y, vertical.width, vertical.height)

    // 滑块
    ctx.fillStyle = vertical.isDragging
      ? this.config.scrollbarHoverColor
      : this.config.scrollbarColor
    ctx.fillRect(
      vertical.x + 2,
      vertical.y + vertical.thumbY,
      vertical.width - 4,
      vertical.thumbHeight
    )
  }

  getScrollbar(): ScrollbarState {
    return this.scrollbar
  }

  setViewport(viewport: ViewPort) {
    this.viewport = viewport
  }

  getViewport(): ViewPort {
    return this.viewport
  }

  getCellAt(x: number, y: number): { row: number; col: number } | null {
    const { departmentColumnWidth, employeeColumnWidth, headerHeight, cellWidth, cellHeight } =
      this.config
    const fixedColumnsWidth = departmentColumnWidth + employeeColumnWidth

    if (x < fixedColumnsWidth || y < headerHeight) return null

    const col = Math.floor((x - fixedColumnsWidth + this.viewport.scrollX) / cellWidth)
    const row = Math.floor((y - headerHeight + this.viewport.scrollY) / cellHeight)

    if (col < 0 || col >= this.dates.length || row < 0 || row >= this.employees.length) {
      return null
    }

    return { row, col }
  }
}
